#define MAX 10000 

#include "BigInteger.h"

#include <iostream>
#include <string>
#include <sstream>
#include <cmath>


NahidaProject::BigInteger::BigInteger()
{
	number = "0";
	sign = false;
}

NahidaProject::BigInteger::BigInteger(std::string s)
{
	if (isdigit(s[0]))
	{
		SetNumber(s);
		sign = false; 
	}
	else
	{
		SetNumber(s.substr(1));
		sign = (s[0] == '-');
	}
}

NahidaProject::BigInteger::BigInteger(std::string s, bool sin)
{
	SetNumber(s);
	SetSign(sin);
}

NahidaProject::BigInteger::BigInteger(int n)
{
	std::stringstream ss;
	std::string s;
	ss << n;
	ss >> s;


	if (isdigit(s[0])) 
	{
		SetNumber(s);
		SetSign(false);
	}
	else
	{
		SetNumber(s.substr(1));
		SetSign(s[0] == '-');
	}
}

void NahidaProject::BigInteger::SetNumber(std::string s)
{
	number = s;
}

const std::string& NahidaProject::BigInteger::GetNumber()
{
	return number;
}

void NahidaProject::BigInteger::SetSign(bool s)
{
	sign = s;
}

const bool& NahidaProject::BigInteger::GetSign()
{
	return sign;
}

NahidaProject::BigInteger NahidaProject::BigInteger::Absolute()
{
	return BigInteger(GetNumber()); // +ve by default
}

void NahidaProject::BigInteger::operator = (BigInteger b)
{
	SetNumber(b.GetNumber());
	SetSign(b.GetSign());
}

bool NahidaProject::BigInteger::operator == (BigInteger b)
{
	return Equals((*this), b);
}

bool NahidaProject::BigInteger::operator != (BigInteger b)
{
	return !Equals((*this), b);
}

bool NahidaProject::BigInteger::operator > (BigInteger b)
{
	return Greater((*this), b);
}

bool NahidaProject::BigInteger::operator < (BigInteger b)
{
	return Less((*this), b);
}

bool NahidaProject::BigInteger::operator >= (BigInteger b)
{
	return Equals((*this), b) || Greater((*this), b);
}

bool NahidaProject::BigInteger::operator <= (BigInteger b)
{
	return Equals((*this), b) || Less((*this), b);
}

NahidaProject::BigInteger& NahidaProject::BigInteger::operator ++()
{
	(*this) = (*this) + 1;
	return (*this);
}

NahidaProject::BigInteger NahidaProject::BigInteger::operator ++(int)
{
	BigInteger before = (*this);

	(*this) = (*this) + 1;

	return before;
}

NahidaProject::BigInteger& NahidaProject::BigInteger::operator --()
{
	(*this) = (*this) - 1;
	return (*this);

}

NahidaProject::BigInteger NahidaProject::BigInteger::operator --(int)
{
	BigInteger before = (*this);

	(*this) = (*this) - 1;

	return before;
}

NahidaProject::BigInteger NahidaProject::BigInteger::operator + (BigInteger b)
{
	BigInteger addition;
	if (GetSign() == b.GetSign())
	{
		addition.SetNumber(Add(GetNumber(), b.GetNumber()));
		addition.SetSign(GetSign());
	}
	else // sign different
	{
		if (Absolute() > b.Absolute())
		{
			addition.SetNumber(Subtract(GetNumber(), b.GetNumber()));
			addition.SetSign(GetSign());
		}
		else
		{
			addition.SetNumber(Subtract(b.GetNumber(), GetNumber()));
			addition.SetSign(b.GetSign());
		}
	}
	if (addition.GetNumber() == "0")
		addition.SetSign(false);

	return addition;
}

NahidaProject::BigInteger NahidaProject::BigInteger::operator - (BigInteger b)
{
	b.SetSign(!b.GetSign());
	return (*this) + b;
}

NahidaProject::BigInteger NahidaProject::BigInteger::operator * (BigInteger b)
{
	BigInteger mul;

	mul.SetNumber(Multiply(GetNumber(), b.GetNumber()));
	mul.SetSign(GetSign() != b.GetSign());

	if (mul.GetNumber() == "0")
		mul.SetSign(false);

	return mul;
}

NahidaProject::BigInteger NahidaProject::BigInteger::operator / (BigInteger b)
{
	long long den = ToInt(b.GetNumber());
	BigInteger div;

	div.SetNumber(Divide(GetNumber(), den).first);
	div.SetSign(GetSign() != b.GetSign());

	if (div.GetNumber() == "0")
		div.SetSign(false);

	return div;
}

NahidaProject::BigInteger NahidaProject::BigInteger::operator % (BigInteger b)
{
	long long den = ToInt(b.GetNumber());

	BigInteger rem;
	long long rem_int = Divide(number, den).second;
	rem.SetNumber(ToString(rem_int));
	rem.SetSign(GetSign() != b.GetSign());

	if (rem.GetNumber() == "0")
		rem.SetSign(false);

	return rem;
}

NahidaProject::BigInteger& NahidaProject::BigInteger::operator += (BigInteger b)
{
	(*this) = (*this) + b;
	return (*this);
}

NahidaProject::BigInteger& NahidaProject::BigInteger::operator -= (BigInteger b)
{
	(*this) = (*this) - b;
	return (*this);
}

NahidaProject::BigInteger& NahidaProject::BigInteger::operator *= (BigInteger b)
{
	(*this) = (*this) * b;
	return (*this);
}

NahidaProject::BigInteger& NahidaProject::BigInteger::operator /= (BigInteger b)
{
	(*this) = (*this) / b;
	return (*this);
}

NahidaProject::BigInteger& NahidaProject::BigInteger::operator %= (BigInteger b)
{
	(*this) = (*this) % b;
	return (*this);
}

NahidaProject::BigInteger& NahidaProject::BigInteger::operator [] (int n)
{
	return *(this + (n * sizeof(BigInteger)));
}

NahidaProject::BigInteger NahidaProject::BigInteger::operator -()
{
	return (*this) * -1;
}

NahidaProject::BigInteger::operator std::string(){
	std::string signedString = (GetSign()) ? "-" : "";
	signedString += number;
	return signedString;
}

bool NahidaProject::BigInteger::Equals(BigInteger n1, BigInteger n2){
	return n1.GetNumber() == n2.GetNumber() && n1.GetSign() == n2.GetSign();
}

bool NahidaProject::BigInteger::Less(BigInteger n1, BigInteger n2)
{
	bool sign1 = n1.GetSign();
	bool sign2 = n2.GetSign();

	if (sign1 && !sign2)
		return true;

	else if (!sign1 && sign2)
		return false;

	else if (!sign1)
	{
		if (n1.GetNumber().length() < n2.GetNumber().length())
			return true;
		if (n1.GetNumber().length() > n2.GetNumber().length())
			return false;
		return n1.GetNumber() < n2.GetNumber();
	}
	else
	{
		if (n1.GetNumber().length() > n2.GetNumber().length())
			return true;
		if (n1.GetNumber().length() < n2.GetNumber().length())
			return false;
		return n1.GetNumber().compare(n2.GetNumber()) > 0;
	}
}

bool NahidaProject::BigInteger::Greater(BigInteger n1, BigInteger n2)
{
	return !Equals(n1, n2) && !Less(n1, n2);
}

std::string NahidaProject::BigInteger::Add(std::string number1, std::string number2)
{
	std::string add = (number1.length() > number2.length()) ? number1 : number2;
	char carry = '0';
	int differenceInLength = abs((int)(number1.size() - number2.size()));

	if (number1.size() > number2.size())
		number2.insert(0, differenceInLength, '0');

	else
		number1.insert(0, differenceInLength, '0');

	for (int i = number1.size() - 1; i >= 0; --i)
	{
		add[i] = ((carry - '0') + (number1[i] - '0') + (number2[i] - '0')) + '0';

		if (i != 0)
		{
			if (add[i] > '9')
			{
				add[i] -= 10;
				carry = '1';
			}
			else
				carry = '0';
		}
	}
	if (add[0] > '9')
	{
		add[0] -= 10;
		add.insert(0, 1, '1');
	}
	return add;
}


std::string NahidaProject::BigInteger::Subtract(std::string number1, std::string number2)
{
	std::string sub = (number1.length() > number2.length()) ? number1 : number2;
	int differenceInLength = abs((int)(number1.size() - number2.size()));

	if (number1.size() > number2.size())
		number2.insert(0, differenceInLength, '0');

	else
		number1.insert(0, differenceInLength, '0');

	for (int i = number1.length() - 1; i >= 0; --i)
	{
		if (number1[i] < number2[i])
		{
			number1[i] += 10;
			number1[i - 1]--;
		}
		sub[i] = ((number1[i] - '0') - (number2[i] - '0')) + '0';
	}

	while (sub[0] == '0' && sub.length() != 1) {
		sub.erase(0, 1);
	}

	return sub;
}

std::string NahidaProject::BigInteger::Multiply(std::string n1, std::string n2){
	if (n1.length() > n2.length()) {
		n1.swap(n2);
	}

	std::string res = "0";
	for (int i = n1.length() - 1; i >= 0; --i){
		std::string temp = n2;
		int currentDigit = n1[i] - '0';
		int carry = 0;

		for (int j = temp.length() - 1; j >= 0; --j){
			temp[j] = ((temp[j] - '0') * currentDigit) + carry;

			if (temp[j] > 9){
				carry = (temp[j] / 10);
				temp[j] -= (carry * 10);
			}
			else {
				carry = 0;
			}

			temp[j] += '0';
		}

		if (carry > 0) {
			temp.insert(0, 1, (carry + '0'));
		}

		temp.append((n1.length() - i - 1), '0');

		res = Add(res, temp);
	}

	while (res[0] == '0' && res.length() != 1) {
		res.erase(0, 1);
	}

	return res;
}

std::pair<std::string, long long> NahidaProject::BigInteger::Divide(std::string n, long long den){
	long long rem = 0;
	std::string result; 
	result.resize(MAX);

	for (int indx = 0, len = n.length(); indx < len; ++indx){
		rem = (rem * 10) + (n[indx] - '0');
		result[indx] = rem / den + '0';
		rem %= den;
	}
	result.resize(n.length());

	while (result[0] == '0' && result.length() != 1) {
		result.erase(0, 1);
	}

	if (result.length() == 0) {
		result = "0";
	}

	return make_pair(result, rem);
}


std::string NahidaProject::BigInteger::ToString(long long n){
	std::stringstream ss;
	std::string temp;

	ss << n;
	ss >> temp;

	return temp;
}

long long NahidaProject::BigInteger::ToInt(std::string s){
	long long sum = 0;

	for (int i = 0; i < s.length(); i++) {
		sum = (sum * 10) + (s[i] - '0');
	}

	return sum;
}
